 /**
*  通信兵
* 含义就是把交给他的包（他不管包里是什么东西）送到服务器，并从服务器取回结果
* 自己保证通信的可靠性，如果超时（超时以后就不允许再接收）或者是失败
*最新添加了超时处理
*
*！！重大发现，原来setInterval失效是因为new Singnalman()没有被保存，而被垃圾收集干掉了!所以再执行
*setInterval()里被定时的函数时导致失效？
 * 
 * 这个的用法是可以直接实用，也可以写个子类来继承而实用
 * 
 * 如果是不需要自动重试,则通信失败的事件是不会向外发的
 * 
 * TypeError: Error #1009: 无法访问空对象引用的属性或方法。
	at kgame5.fremoting::FRemoting/traceSendPackage()
	at yoboplayer.dboxsm::MyNet2/onNetCommFail()
	at flash.events::EventDispatcher/dispatchEventFunction()
	at flash.events::EventDispatcher/dispatchEvent()
	at kgame5.fremoting::FRemoting/notifyCommFail()
	at kgame5.fremoting::FRemoting/handleCommFail()
	at kgame5.fremoting::FRemoting/timeout()
	at flash.utils::Timer/_timerDispatch()
	at flash.utils::Timer/tick()
 * 
 * 整理了一下FRemoting,以前这个是有问题的，因为clear里把myPackage=null,导致了以后再次start的时候会出现问题，myPackage=null
 * 了就，其实就是永远也不可能自动连好了。
 * 这次修改就是把clearTimer做好了，在通信成功或者通信失败的时候都要清一下timer,这个是应该这样做的 ok
*/

package kgame5.kio.fremoting {
import kgame5.kutil.*;

import flash.events.*;
import flash.net.*;
import flash.utils.*;

public class FRemoting extends EventDispatcher{
	
	public static var gatewayURL:String;
	public static var servletPath:String;
	
	//事件类型
	public static const
		EVENTTYPE_COMM_OK:String="EVENTTYPE_COMM_OK",
		EVENTTYPE_COMM_FAIL:String="EVENTTYPE_COMM_FAIL",
		//EVENTTYPE_COMMTIMEOUT:String="EVENTTYPE_COMMTIMEOUT",
		EVENTTYPE_AUTORETRY:String="EVENTTYPE_AUTORETRY";
	
	//__________________Property________________________
	
	//--nc.call里用的
	private var nc:NetConnection=null;
	
	//--要传递的信息,
	private var myPackage : Object = null;//注意flashRemoting认为一个有array_x[0]=0,array_x.ttt=""的数组是java的HashMap
	
	//----
	//失败次数记录
	//-
	private var bNeedAutoRetry:Boolean=true;//是否需要自动重连
	
	private var failTimes : int = 0;
	private var MaxFailTimes:int=10;//最大失败次数，超过此次数就认为通信失败，小于次次数则要重试
	
	private var timer_comm:Timer;//计算通信时间,到时间还不通就认为超时
	private var timer_autoRetry:Timer;//等待自动重连
	
	private var Timeout_BaseDur:int=0;//连接超时的基础时间，以后如果再重试，则重试次数是此基础事件与重试次数的函数
	private var AutoRetry_BaseDur:int=0;//重试事件间隔的基础时间
	
	private var restartTimes:int=0;//重启的次数，每次事件都要延长
	//__________________Method______________________
	public function FRemoting (p_package : Object,
									p_bNeedAutoRetry:Boolean,
									Timeout_Dur:int,AutoRetry_Dur:int,p_MaxFailTimes:int
									){
		//trace2("构造函数被执行了");
		nc=new NetConnection();
		
		nc.objectEncoding = ObjectEncoding.AMF0;//使用as1和as2兼容的amf编码
		
		nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
		nc.addEventListener(IOErrorEvent.IO_ERROR , ioErrorHandler);//这可能是由于从 netStatus 事件处理程序中调用了 connect()，而这是不允许的。 
		nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
		nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
		
		try{
			nc.connect(gatewayURL);//"http://192.168.0.109/gateway"
		}catch(e:Error){
			trace2("!!FRemoting的FConnect的时候发生了异常");
		}
		
		
		
		///////////////////////////
		myPackage = p_package;
		
		//--设置自己参数
		bNeedAutoRetry=p_bNeedAutoRetry;
		MaxFailTimes=p_MaxFailTimes;
		
		Timeout_BaseDur=Timeout_Dur;//超时时间
		AutoRetry_BaseDur=AutoRetry_Dur;//自动重连时间
		
		timer_comm=new Timer(Timeout_BaseDur);//超时是5分钟
		timer_autoRetry=new Timer(AutoRetry_BaseDur);//自动重连每隔30秒
		
		
	}
	//显示包里的信息
	public function traceSendPackage():String{
		return "package信息:"+myPackage.iFuncID+","+myPackage.iPackageID;
	}
	public function getSendPackageID():int{
		return myPackage.iPackageID;
	}
	//--
	private function asyncErrorHandler(e:AsyncErrorEvent){
		trace2("asyncErrorHandler");
		//内部先处理通信失败的情况
		handleCommFail();
	}
	private function ioErrorHandler(e:IOErrorEvent){
		trace2("ioErrorHandler");
		//内部先处理通信失败的情况
		handleCommFail();
	}
	
	/*
	"NetConnection.Call.BadVersion" "error" 以不能识别的格式编码的数据包。 
"NetConnection.Call.Failed" "error" NetConnection.call 方法无法调用服务器端的方法或命令。 
"NetConnection.Call.Prohibited" "error" Action Message Format (AMF) 操作因安全原因而被阻止。 或者是 AMF URL 与 SWF 不在同一个域，或者是 AMF 服务器没有信任 SWF 文件的域的策略文件。  
"NetConnection.Connect.Closed" "status" 成功关闭连接。 
"NetConnection.Connect.Failed" "error" 连接尝试失败。 
"NetConnection.Connect.Success" "status" 连接尝试成功。 
"NetConnection.Connect.Rejected" "error" 连接尝试没有访问应用程序的权限。 
"NetConnection.Connect.AppShutdown" "error" 正在关闭指定的应用程序。 
	
	//*/
	private function netStatusHandler(e:NetStatusEvent){
		trace2("netStatusHandler "+","+e.info.level+" "+e.info.code);
		//注意这个理要些处理程序处理的是
		if(e.info.level=="error"){
			//内部先处理通信失败的情况
			handleCommFail();
		}
	}
	private function securityErrorHandler(e:SecurityErrorEvent){
		trace2("securityErrorHandler");
		trace2(e.toString());
		//这个不做处理
	}
	
	// 执行发送并下载数据的操作
	//如果向外报了错，则外部会控制它重新启动一下，此时是吧时间响应的延长
	
	public function start(){
		trace2("start");
		//--设置自己参数							
		timer_comm.delay=Timeout_BaseDur*Math.pow(2,restartTimes);//超时时间
		timer_autoRetry.delay=AutoRetry_BaseDur*Math.pow(2,restartTimes);//自动重连时间
		
		restartTimes++;
		
		//--
		failTimes=0;//失败次数清空,否则重用的时候会以为错了12次呢！
		
		//--
		comm ();
	}
	//清除一下各个timer，以方便下次使用
	private function clearTimer(){
		//myPackage=null;
		//trace2("clear销毁对象 "+(myPackage==null));
		//自己计算连接超时的计时器
		timer_comm.stop();
		timer_comm.removeEventListener(TimerEvent.TIMER,timeout);
		//失败重连的计时器
		timer_autoRetry.stop();
		timer_autoRetry.removeEventListener(TimerEvent.TIMER,autoRetry);
		
		//bNeedAutoRetry=true;//默认是要自动重连的
	}
	
	//----执行一次通信
	private function comm (){
		trace2("------comm建立一次通信------");
		// 通过类名调用Servlet
     	// 例如，如果Servlet引擎接收了来自 /servlet/com.foo.servlet.MailServlet的请求，Servlet引擎会载入这个 com.foo.servlet.MailServlet类，建立实例，并通过这个Servlet来处理请求。
		//nc.call("/.MyServlet",new Responder(onResult2,onFault2),createPackage(8))
		nc.call (servletPath, new Responder(resultHandler,faultHandler),myPackage);
	
		//设置超时时间
		timer_comm.start();
		timer_comm.addEventListener(TimerEvent.TIMER,timeout);
		
		//计算时间用的
		startTime=getTimer();
	}
	
	//这个是计算联网时间用的
	private var startTime:int;
	//----
	private function resultHandler (re :Object){
		
		trace2("耗时 "+(getTimer()-startTime)/1000+" 秒"+"-----!!!resultHandler通信成功 packageID:"+re.iPackageID+"!!!---------");
		trace("---返回结果:---");
		trace(re.iFuncID,re.iPackageID);
		for(var i in re){
			trace(i,re[i]);
		}
		trace("");
		
		//清除各个timer
		clearTimer();
		
		//!!注意这里的if是很重要的东西啊！！他保证的是如果一个被判定为超时的包又回来了的处理情况
		//如果已经不是当前正发送的包回来了，则不处理，因为它早已被抛弃了！
		//如果是当前正在发送的包回来了（不关它是否是被判定超时了的，我的目的不就是要发个请求要响应吗，且是同一请求的响应，用哪个不都一样啊，用完了此signalmen任务完成，撤退了啊就，等待新的任务）
		if(myPackage!=null&&re.iPackageID==myPackage.iPackageID){
			//通知说通信成功
			notifyCommOK (re);
			
			
		}else{
			trace2("此packege是过期的了，直接丢弃哦");
		}
	}
	//----
	private function faultHandler (fe : Object){
		//Part 2:输出调试信息
		trace2("------!!!faultHandler!!!-------");
		
		for(var i in fe){
			trace2(fe[i]);
		}
		
		//内部先处理通信失败的情况
		handleCommFail();
	}
	//
	private function timeout(e:TimerEvent){
		trace2("-----timeout网络连接超时-----");
		
		//自己计算连接超时的计时器,计时到了就关闭
		timer_comm.stop();
		timer_comm.removeEventListener(TimerEvent.TIMER,timeout);
		
		//内部先处理通信失败的情况，就是在尝试10次
		handleCommFail();
	}
	
	//当通信失败的时候，注意可能是remoting报错也可能是网络通信时间超时
	private function handleCommFail(){
		trace2("---handleCommFail---");
		
		//把定时器都释放一下，超时时间的timer需要释放，自动重连的timer需要释放
		clearTimer();
		
		if(bNeedAutoRetry){//如果需要出错自动重连则出错自动重连
			//Part 1:出错的处理
			if (failTimes ++ >= MaxFailTimes){
				trace2("!!!对外通知连接失败");
				//通信失败次数超过10次则已经尽了最大努力而通信仍然失败,同志司令官说通信失败
				notifyCommFail (myPackage);
		
				
			}else{
				trace2("得到连接失败事件，启动计时，稍等片刻则自动重连");
				//失败了等几分钟继续尝试通信
				timer_autoRetry.start();
				timer_autoRetry.addEventListener(TimerEvent.TIMER,autoRetry);
			}
		}else{
			trace2("-----非关键数据出错，直接关闭即可-----");
			//注意每次用完了清除资源是必须的，否则这个得不到释放会造成内存泄露
			//clear();//销毁对象，释放资源
		}
	}
	//
	private function autoRetry(e:TimerEvent){
		trace2("-----自动重试------");
		//失败重连的计时器要清除
		timer_autoRetry.stop();
		timer_autoRetry.removeEventListener(TimerEvent.TIMER,autoRetry);
		//
		comm();
		
		//通知说在自动重连
		notifyAutoRetry(myPackage);
	}
	
	//***************************向外发送事件********************
	private function notifyCommOK (result:Object){//被服务器返回的结果r.iFuncInfo,r.funcStatus等
		var ke:KEvent=new KEvent(EVENTTYPE_COMM_OK,{result:result});
		this.dispatchEvent(ke);
	}
	private  function notifyCommFail (myPackage:Object){
		trace2("notifyCommFail ");
		var ke:KEvent=new KEvent(EVENTTYPE_COMM_FAIL,{myPackage:myPackage});
		this.dispatchEvent(ke);
	}
	/*
	public  function notifyCommTimeout (myPackage:Object){
		var ke:KEvent=new KEvent(EVENTTYPE_COMMTIMEOUT,
			{
				myPackage:myPackage
			}
		);
		this.dispatchEvent(ke);
	}
	//*/
	private  function notifyAutoRetry(myPackage:Object){//失败后在自动重连
		var ke:KEvent=new KEvent(EVENTTYPE_AUTORETRY,{myPackage:myPackage});
		this.dispatchEvent(ke);
	}
	//--------调试用的
	private function trace2(sTip:String){
		T.p("FRemoting:",sTip);
//		if(myPackage!=null){
//			T.p("pid:"+myPackage.iPackageID,"bNeedAutoRetry:"+bNeedAutoRetry,"fid:"+myPackage.iFuncID,"failTimes:"+failTimes);
//		}
	}
} //end of class
}